#include "Renderer.h"

#include "Game.h"

#include <iostream>

int Renderer::mainWindowHandle = 0;
int Renderer::perfTestWindowHandle = 0;
DevilTexture Renderer::texture[Textures::TEXTURES_COUNT];
GLUquadricObj* Renderer::quadric = NULL;
bool Renderer::texturesEnabled = false;
int Renderer::currentTextureId = -1;
int Renderer::currentTime = 0;
int Renderer::elapsedFpsTime = 0;
int Renderer::frameCount = 0;
int Renderer::fps = 0;
int Renderer::currentMouseX = 0;
int Renderer::currentMouseY = 0;

bool Renderer::anisotropySupported = false;
float Renderer::maxAnisotropy = 0.0f;

SurfaceRenderingData Renderer::shadowMaterial;

void Renderer::Init(int argc, char** argv, int width, int height, const char* windowTitle) {
	glutInit(&argc, argv);
	glutInitWindowSize(width, height);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
	mainWindowHandle = glutCreateWindow(windowTitle);
	glutSetWindow(mainWindowHandle);
	//glutFullScreen();
	GLenum err = glewInit();
	if (err != GLEW_OK) {
		Game::log.LogError(StringBuilder() << glewGetErrorString(err));
	}

	// Setup default render states
	glClearColor(0.9f, 0.95f, 1.0f, 1.0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	glShadeModel(GL_SMOOTH);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glEnable(GL_NORMALIZE);
	glEnable(GL_LIGHTING);

	//Anisotropy
	if (!strstr((char*)glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) {
		anisotropySupported = false;
		Game::log.LogInfo(StringBuilder() << "Anisotropic Filtering is not supported");
	} else {
		anisotropySupported = true;
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
		Game::log.LogInfo(StringBuilder() << "Anisotropic Filtering: " << maxAnisotropy);
	}

	//Textures
	Textures::Init();
	glEnable(GL_TEXTURE_2D);
	for(int i=0; i<Textures::TEXTURES_COUNT; ++i) {
		LoadTexture(i, true);
	}

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	//Quadric
	quadric = gluNewQuadric();
	gluQuadricDrawStyle(quadric,GLU_FILL);
	gluQuadricNormals(quadric,GLU_SMOOTH);  // Ein Normalenvektor pro Vertex
	gluQuadricTexture(quadric,GL_TRUE);  // Textur Koordinaten erzeugen

	glutReshapeFunc(ReshapeCallback);
	glutIdleFunc(IdleCallback);
	glutDisplayFunc(RenderCallback);
	glutKeyboardFunc(KeyboardCallback);
	glutMouseFunc(MouseClickCallback);
	glutSpecialFunc(KeyboardSpecialCallback);
	glutMotionFunc(MouseMotionCallback);
	glutPassiveMotionFunc(MousePassiveMotionCallback);

	//Menu
	glutCreateMenu(MenuCallback);
	glutAddMenuEntry("Hilfe", MENU_HELP);
	glutAddMenuEntry("Debug Rendering", MENU_DEBUG_RENDERING);
	glutAddMenuEntry("HUD", MENU_HUD);
	glutAddMenuEntry("Kanonenkugel Kamera", MENU_CANNON_BALL_CAMERA);
	glutAttachMenu(GLUT_MIDDLE_BUTTON);

	//Materials
	shadowMaterial.SetColor(0.0f, 0.0f, 0.0f, 0.8f);
}

void Renderer::Run() {
	glutMainLoop();
}

void Renderer::ReshapeCallback(int width, int height) {
	glViewport(0, 0, width, height);
	PhysicsLogic::GetCamera().SetAspectRatio((float)width/(float)height);
}

void Renderer::IdleCallback() {
	if(perfTestWindowHandle != 0) {
		glutSetWindow(perfTestWindowHandle);
		glutPostRedisplay();
	}
	glutSetWindow(mainWindowHandle);
	PhysicsLogic::SimulateAndRender();
	//glutPostRedisplay();
}

void Renderer::RenderCallback() {
	
}

void Renderer::KeyboardCallback(unsigned char key, int x, int y) {
	PhysicsLogic::ProcessKeyboardInput(key, x, y);
}

void Renderer::KeyboardSpecialCallback(int key, int x, int y) {
	PhysicsLogic::ProcessKeyboardSpecialInput(key, x, y);
}

void Renderer::MouseClickCallback(int button, int state, int x, int y) {
	PhysicsLogic::ProcessMouseClick(button, state, x, y);
}

void Renderer::MouseMotionCallback(int x, int y) {
	PhysicsLogic::ProcessMouseState(x, y);
	int dx = x - currentMouseX;
	int dy = y - currentMouseY;
	PhysicsLogic::ProcessMouseMovement(dx, dy);
	
	currentMouseX = x;
	currentMouseY = y;
}

void Renderer::MousePassiveMotionCallback(int x, int y) {
	currentMouseX = x;
	currentMouseY = y;
}

void Renderer::MenuCallback(int option) {
	PhysicsLogic::ProcessMenuInput(option);
}

/*void GlutRenderer::SetTexture(int id) {
	
	if(!texturesEnabled) {
		glEnable(GL_TEXTURE_2D);
		glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		texturesEnabled = true;
	}
	if(currentTextureId != id) {
		glBindTexture(GL_TEXTURE_2D, textureIds[id]);
		currentTextureId = id;
	}
}*/

/*void GlutRenderer::SetColor(float r, float g, float b, float a) {
	if(texturesEnabled) {
		glDisable(GL_TEXTURE_2D);
		texturesEnabled = false;
	}
	glColor4f(r, g, b, a);


	//float specReflection[] = { 1, 1, 1, 1 };
	//glMaterialfv(GL_FRONT, GL_SPECULAR, specReflection);
	//glMateriali(GL_FRONT, GL_SHININESS, 10);
}*/

void Renderer::ClearBuffers() {
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}

void Renderer::SetLight(const Light& light) {
	glLightfv(light.id, GL_AMBIENT, light.ambient);
	glLightfv(light.id, GL_DIFFUSE, light.diffuse);
	glLightfv(light.id, GL_SPECULAR, light.specular);
	glLightfv(light.id, GL_POSITION, light.position);
}

void Renderer::SetLightState(const Light& light, bool enabled) {
	if(enabled) {
		glEnable(light.id);
	} else {
		glDisable(light.id);
	}
}

void Renderer::Update(const Camera& camera) {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(camera.GetFovyAngle(), camera.GetAspectRatio(), camera.GetzNear(), camera.GetzFar());
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	const NxVec3& pos = camera.GetPosition();
	const NxVec3& dir = camera.GetDirection();
	const NxVec3& up = camera.GetUpNormal();
	gluLookAt(pos.x, pos.y, pos.z, pos.x + dir.x, pos.y + dir.y, pos.z + dir.z, up.x, up.y, up.z);
}

void Renderer::SetMaterial(const SurfaceRenderingData& material) {
	if(material.textureId >= 0) {
		texture[material.textureId].Use();
	} else {
		glDisable(GL_TEXTURE_2D);
	}
	glColor4fv(material.color);

	//glMaterialfv(GL_FRONT, GL_DIFFUSE, material.diffuseMaterial);
	//glMaterialfv(GL_FRONT, GL_AMBIENT, material.ambientMaterial);

	//glMaterialfv(GL_FRONT, GL_EMISSION, material.emissiveMaterial);
	glMaterialfv(GL_FRONT, GL_SPECULAR, material.specularMaterial);
	glMaterialfv(GL_FRONT, GL_SHININESS, material.shininess);
}

void Renderer::ResetMaterial() {
	glDisable(GL_TEXTURE_2D);

	static const float v1[] = {1,1,1,1};
	static const float v0[] = {0,0,0,0};
	glColor4fv(v1);
	glMaterialfv(GL_FRONT, GL_SPECULAR, v0);
	glMaterialfv(GL_FRONT, GL_EMISSION, v0);
}

void Renderer::DrawBox(const NxVec3& dim) {
	glBegin(GL_QUADS);

	// Vorderseite
	glNormal3f(0.0f, 0.0f, 1.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(dim.x, dim.y, dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-dim.x, dim.y, dim.z);

	// rechte Seite
	glNormal3f(1.0f, 0.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(dim.x, -dim.y, -dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(dim.x, dim.y, -dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(dim.x, dim.y, dim.z);

	// Hinterseite
	glNormal3f(0.0f, 0.0f, -1.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(dim.x, -dim.y, -dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-dim.x, -dim.y, -dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-dim.x, dim.y, -dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(dim.x, dim.y, -dim.z);

	// Linke Seite
	glNormal3f(-1.0f, 0.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-dim.x, -dim.y, -dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-dim.x, dim.y, dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-dim.x, dim.y, -dim.z);

	// Deckel
	glNormal3f(0.0f, 1.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-dim.x, dim.y, dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(dim.x, dim.y, dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(dim.x, dim.y, -dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-dim.x, dim.y, -dim.z);

	// Unterseite
	glNormal3f(0.0f, -1.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f(-dim.x, -dim.y, dim.z);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f(-dim.x, -dim.y, -dim.z);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(dim.x, -dim.y, -dim.z);

	glEnd();
}

void Renderer::DrawSphere(float size) {
	gluSphere(quadric, size, 16, 16);
}

void Renderer::DrawPoint(float size) {
	//glEnable(GL_POINT_SMOOTH);
	glPointSize(size);
	glBegin(GL_POINTS);
	glVertex3f(0,0,0);
	glEnd();
}

void Renderer::DrawPlane(float size, int iterations, float height) {
	glBegin(GL_QUADS);
	for(int x=-iterations; x<=iterations; ++x) {
		for(int z=-iterations; z<=iterations; ++z) {
			float ox = size*2.0f*x;
			float oz = size*2.0f*z;
			
			glNormal3f(0.0f, 1.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(-size+ox, height, size+oz);
			glTexCoord2f(1.0f, 0.0f);
			glVertex3f(size+ox, height, size+oz);
			glTexCoord2f(1.0f, 1.0f);
			glVertex3f(size+ox, height, -size+oz);
			glTexCoord2f(0.0f, 1.0f);
			glVertex3f(-size+ox, height, -size+oz);
		}
	}
	glEnd();
}

void Renderer::LoadTexture(int index, bool anisotropy) {
	texture[index].Load(Textures::path[index].c_str(), anisotropy);
}
